//
//  ScratchProtocol.m
//  EsotericDemo
//
//  Created by Temp Admin on 6/12/12.
//  Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//

#import "ScratchClient.h"

#define ncpyto16(buffer,item) { uint16_t data = htons(item); memcpy(buffer, &data, 2); }
#define ncpyto32(buffer,item) { uint32_t data = htonl(item); memcpy(buffer, &data, 4); }
#define ncpyfrom16(buffer,item) { uint16_t data; memcpy(&data, buffer, 2); item = ntohs(data); }
#define ncpyfrom32(buffer,item) { uint32_t data; memcpy(&data, buffer, 4); item = ntohl(data); }

@implementation ScratchClient
@synthesize key, vMajor, vMinor, useSessionIds, port, writeLock;

- (id) init
{
	if (self = [super init]) 
	{
		incoming = [[NSMutableData alloc] init];
		outgoing = [[NSMutableData alloc] init];
	}
	return self;
}

- (id) initWithInfo : (ScratchEvents *) setEvents
{
	if (self = [super init])
	{
		events = setEvents;
		[events setCore:self];
		incoming = [[NSMutableData alloc] init];
		outgoing = [[NSMutableData alloc] init];
	}
	return self;
}

- (void) connect : (CFStringRef) ip
{
	CFReadStreamRef readStream;
	CFWriteStreamRef writeStream;
	
	CFStreamCreatePairWithSocketToHost(NULL, ip, port, &readStream, &writeStream);
	
	inputStream = (__bridge NSInputStream *)readStream;
	outputStream = (__bridge NSOutputStream *)writeStream;
	
	[inputStream setDelegate:self];
	[outputStream setDelegate:self];
	
	[inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
	[outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
	
	[inputStream open];
	[outputStream open];
}

- (void) stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent 
{
	switch (streamEvent) 
    {
		case NSStreamEventOpenCompleted:
			break;
            
		case NSStreamEventHasBytesAvailable:
            if (theStream == inputStream)
            {
                while ([inputStream hasBytesAvailable])
                {
                    [self receiveData];
                }
            }
			break;			
            
		case NSStreamEventErrorOccurred:
			[events connectionLost];
			break;
            
		case NSStreamEventEndEncountered:
			[events connectionLost];
			break;
            
        case NSStreamEventHasSpaceAvailable:
			if (theStream == outputStream)
			{
				spaceOpen = true;
				[self processOutgoing];
			}
            break;
	}
}

- (void) Close
{
	[inputStream close];
	[outputStream close];
}

- (void) Send: (ScratchMessage *) message
{
	NSData * rawdata = [message getData];
	uint8_t outdata[19 + [rawdata length]];
	
	ncpyto32(outdata, key);
	outdata[4] = vMajor;
	outdata[5] = vMinor;
	ncpyto32(outdata+6, SessionID);
	ncpyto16(outdata+10, message->Flags);
	outdata[12] = message->Channel;
	ncpyto16(outdata+13, message->PrimaryCommand);
	ncpyto16(outdata+15, message->SecondaryCommand);
	ncpyto16(outdata+17, [rawdata length]);
	[rawdata getBytes:outdata+19 length:[rawdata length]];
	
	[outgoing appendBytes:outdata length:19 + [rawdata length]];
	[self processOutgoing];
}

- (void) handleMessage: (ScratchMessage *) message
{
	if ([message getFlag:PS_INTERNAL])
	{
		switch (message->PrimaryCommand)
		{
			case PS_CONFIG:
				SessionID = message->SessionID;
				[self confirm];
				break;
				
			case PS_PINGPONG:
				if (message->SecondaryCommand == PS_PING)
					[self pong];
				break;
		}
	}
	else 
	{
		[events messageReceived:message];
	}
	
}

- (void) confirm
{
	ScratchMessage * confirmMessage = [[ScratchMessage alloc] init];;
	
	[confirmMessage setFlag:PS_INTERNAL :true];
	confirmMessage->PrimaryCommand = PS_CONFIG;
	confirmMessage->SecondaryCommand = PS_CONFIRM;
	[self Send:confirmMessage];
	[events newConnection];
}

- (void) pong
{
	ScratchMessage * confirmMessage = [[ScratchMessage alloc] init];;
	
	[confirmMessage setFlag:PS_INTERNAL :true];
	confirmMessage->PrimaryCommand = PS_PINGPONG;
	confirmMessage->SecondaryCommand = PS_PONG;
	[self Send:confirmMessage];
}

- (void) receiveData
{
	uint8_t data[1024];
	int readAmount = [inputStream read:data maxLength:1024];
	if (readAmount > 0)
	{
		[incoming appendBytes:data length:readAmount];
		while ([self processIncoming]);
	}
}

- (bool) processIncoming
{
	if ([incoming length] < 4) 
		return false;
	uint8_t * rawdata = (uint8_t *)[incoming bytes];
	int32_t t_key;
	ncpyfrom32(rawdata, t_key);
	if (t_key != key)
	{
		[incoming replaceBytesInRange:NSMakeRange(0, 1) withBytes:NULL length:0];
		return false;
	}
	if ([incoming length] < 19) 
		return false;
	uint16_t frameLength;
	ncpyfrom16(rawdata+17, frameLength);
	if ([incoming length] < 19 + frameLength) 
		return false;

	ScratchMessage * message = [[ScratchMessage alloc] init];
	uint8_t t_vMajor, t_vMinor;
	
	t_vMajor = rawdata[4];
	t_vMinor = rawdata[5];
	ncpyfrom32(rawdata+6, message->SessionID);
	ncpyfrom16(rawdata+10, message->Flags);
	message->Channel = rawdata[12];
	ncpyfrom16(rawdata+13, message->PrimaryCommand);
	ncpyfrom16(rawdata+15, message->SecondaryCommand);
	[message packBytes:rawdata+19 :frameLength];
	[self handleMessage:message];
	[incoming replaceBytesInRange:NSMakeRange(0, frameLength + 19) withBytes:NULL length:0];
	return true;
}

- (void) processOutgoing
{
	if (spaceOpen && [outgoing length] > 0)
	{
		int sendSize = [outgoing length] > 1024 ? 1024 : [outgoing length];
		int sent = 0;
		uint8_t outBuffer[sendSize];
		[outgoing getBytes:outBuffer length:sendSize];
		sent = [outputStream write:outBuffer maxLength:sendSize];
		if (sent < 0) return; //error, will be caught by error event
		[outgoing replaceBytesInRange:NSMakeRange(0, sent) withBytes:NULL length:0];
		spaceOpen = false;
	}
}

@end
